package com.xiyuan.smartutils.crypto;

import com.xiyuan.smartutils.Asserts;
import com.xiyuan.smartutils.Bytes;
import com.xiyuan.smartutils.Hexs;
import com.xiyuan.smartutils.constants.CodeConstants;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5编码，调用JDK的MD5编码
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */
public class MD5 implements CodeConstants {
    
    
    /**
     * MD5 编码，UTF8
     *
     * @param src 源串
     * @return 目标串
     */
    public static String encodeUTF8(String src) {
        return encode(src, _UTF_8_C_);
    }
    
    /**
     * MD5编码，指定encding
     *
     * @param src      源串
     * @param encoding 编码
     * @return 目标串
     */
    public static String encode(String src, String encoding) {
        return HEX.toString(encrypt(src, encoding));
    }
    
    /**
     * MD5编码，指定encding
     *
     * @param src     源串
     * @param charset 字符集
     * @return 目标串
     */
    public static String encode(String src, Charset charset) {
        return HEX.toString(encrypt(src, charset));
    }
    
    /**
     * MD5编码，指定encding
     *
     * @param src 源字节数组
     * @return 目标字节数组
     */
    public static String encode(byte[] src) {
        return HEX.toString(encrypt(src));
    }
    
    /**
     * MD5编码,返回byte数组
     *
     * @param src      源串
     * @param encoding 编码
     * @return 目标字节数组
     */
    public static byte[] encrypt(String src, String encoding) {
        Asserts.nonNull(encoding, _ENCODING_);
        return encrypt(src, Charset.forName(encoding));
    }
    
    /**
     * MD5编码,返回byte数组
     *
     * @param src     源串
     * @param charset 字符集
     * @return 目标字节数组
     */
    public static byte[] encrypt(String src, Charset charset) {
        Asserts.nonNull(src, _SRC_);
        Asserts.nonNull(charset, _CHARSET_);
        return encrypt(src.getBytes(charset));
    }
    
    /**
     * MD5编码,返回byte数组
     *
     * @param src 源字节数组
     * @return 目标字节数组
     */
    public static byte[] encrypt(byte[] src) {
        Asserts.nonNull(src, _SRC_);
        return MD5Coder.encodeByte(src);
        
    }
    
    /**
     * 获取MD5消息摘要实例
     *
     * @return MD5消息摘要实例
     */
    private static MessageDigest getInstance() throws NoSuchAlgorithmException {
        try {
            return MessageDigest.getInstance(_MD5_);
        }
        catch (NoSuchAlgorithmException e) {
            throw Asserts.exception("JDK不支持[" + _MD5_ + "]算法");
        }
    }
    
    
    @SuppressWarnings("SpellCheckingInspection")
    public static class MD5Coder {
        /** 补位字节，第1位为1其他为0 */
        private static final byte PAD   = (byte)0x80;
        
        /** 4个初始化值 */
        private static final int[] V    = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
        
        /** 4*4的矩阵 */
        private static final int[] S    = {7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21};
        
        /** 4*16次操作值 */
        private static final int[] T    =  {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
                                            0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
                                            0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
                                            0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
                                            0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
                                            0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
                                            0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
                                            0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
        
        //实例初始化时，初值为固定的值
        private final int[] abcd = {V[0], V[1], V[2], V[3]};
        
        /***************************************************************************************/
        // 以下为MD5实例实现方法 digest & update
        /***************************************************************************************/
        
        /**
         * 核心处理方法
         *
         * @param bytes 传入的字节数组
         * @return      处理之后得到的MD5码
         */
        private byte[] digest(byte[] bytes)
        {
            //第一步，计算分组数目，每64字节一组，+8表示最后8个字节放原始字节长度，当bytes.length/64=0时要强制增加一组，其他情况补齐即可
            int num = ((bytes.length + 8) / 64) + 1;
            
            //第二步，按要求建立新的分组字节数组，分别把原始数据、补位码和原始数据大小按要求小端格式设置好
            byte[] buffer = new byte[64 * num];
            Bytes.putBytes(buffer, 0, bytes);
            Bytes.putByte(buffer, bytes.length, PAD);// 补位
            Bytes.LU.putLong(buffer, buffer.length - 8, bytes.length * 8L);
            
            //第三步，把字节数组转换成整数
            int[] m = new int[16 * num];
            for (int i=0;i<16*num;i++){
                m[i] = Bytes.LU.getInt(buffer, i * 4);
            }
            buffer = null;
            
            //第四步，按每组16个整数(64字节)进行更新abcd的数字签名
            for (int i=0;i<num;i++){
                update(slice(m, i*16, (i+1)*16));
            }
            
            //第五步，把得到的abcd数字签名转化128位16个字节数组，返回结果
            byte[] result = new byte[16];
            Bytes.LU.putInt(result, 0, abcd[0]);
            Bytes.LU.putInt(result, 4, abcd[1]);
            Bytes.LU.putInt(result, 8, abcd[2]);
            Bytes.LU.putInt(result, 12, abcd[3]);
            return result;
        }
        
        /**
         * 分组更新abcd数字签名
         *
         * @param x 一组16个整数(64字节)
         */
        private void update(int[] x)
        {
            int a = abcd[0], b = abcd[1], c = abcd[2], d = abcd[3];
            
            // 第一轮
            a = ff(a, b, c, d, x[0],  S[0],  T[0]);
            d = ff(d, a, b, c, x[1],  S[1],  T[1]);
            c = ff(c, d, a, b, x[2],  S[2],  T[2]);
            b = ff(b, c, d, a, x[3],  S[3],  T[3]);
            a = ff(a, b, c, d, x[4],  S[0],  T[4]);
            d = ff(d, a, b, c, x[5],  S[1],  T[5]);
            c = ff(c, d, a, b, x[6],  S[2],  T[6]);
            b = ff(b, c, d, a, x[7],  S[3],  T[7]);
            a = ff(a, b, c, d, x[8],  S[0],  T[8]);
            d = ff(d, a, b, c, x[9],  S[1],  T[9]);
            c = ff(c, d, a, b, x[10], S[2],  T[10]);
            b = ff(b, c, d, a, x[11], S[3],  T[11]);
            a = ff(a, b, c, d, x[12], S[0],  T[12]);
            d = ff(d, a, b, c, x[13], S[1],  T[13]);
            c = ff(c, d, a, b, x[14], S[2],  T[14]);
            b = ff(b, c, d, a, x[15], S[3],  T[15]);
            // 第二轮
            a = gg(a, b, c, d, x[1],  S[4],  T[16]);
            d = gg(d, a, b, c, x[6],  S[5],  T[17]);
            c = gg(c, d, a, b, x[11], S[6],  T[18]);
            b = gg(b, c, d, a, x[0],  S[7],  T[19]);
            a = gg(a, b, c, d, x[5],  S[4],  T[20]);
            d = gg(d, a, b, c, x[10], S[5],  T[21]);
            c = gg(c, d, a, b, x[15], S[6],  T[22]);
            b = gg(b, c, d, a, x[4],  S[7],  T[23]);
            a = gg(a, b, c, d, x[9],  S[4],  T[24]);
            d = gg(d, a, b, c, x[14], S[5],  T[25]);
            c = gg(c, d, a, b, x[3],  S[6],  T[26]);
            b = gg(b, c, d, a, x[8],  S[7],  T[27]);
            a = gg(a, b, c, d, x[13], S[4],  T[28]);
            d = gg(d, a, b, c, x[2],  S[5],  T[29]);
            c = gg(c, d, a, b, x[7],  S[6],  T[30]);
            b = gg(b, c, d, a, x[12], S[7],  T[31]);
            // 第三轮
            a = hh(a, b, c, d, x[5],  S[8],  T[32]);
            d = hh(d, a, b, c, x[8],  S[9],  T[33]);
            c = hh(c, d, a, b, x[11], S[10], T[34]);
            b = hh(b, c, d, a, x[14], S[11], T[35]);
            a = hh(a, b, c, d, x[1],  S[8],  T[36]);
            d = hh(d, a, b, c, x[4],  S[9],  T[37]);
            c = hh(c, d, a, b, x[7],  S[10], T[38]);
            b = hh(b, c, d, a, x[10], S[11], T[39]);
            a = hh(a, b, c, d, x[13], S[8],  T[40]);
            d = hh(d, a, b, c, x[0],  S[9],  T[41]);
            c = hh(c, d, a, b, x[3],  S[10], T[42]);
            b = hh(b, c, d, a, x[6],  S[11], T[43]);
            a = hh(a, b, c, d, x[9],  S[8],  T[44]);
            d = hh(d, a, b, c, x[12], S[9],  T[45]);
            c = hh(c, d, a, b, x[15], S[10], T[46]);
            b = hh(b, c, d, a, x[2],  S[11], T[47]);
            // 第四轮
            a = ii(a, b, c, d, x[0],  S[12], T[48]);
            d = ii(d, a, b, c, x[7],  S[13], T[49]);
            c = ii(c, d, a, b, x[14], S[14], T[50]);
            b = ii(b, c, d, a, x[5],  S[15], T[51]);
            a = ii(a, b, c, d, x[12], S[12], T[52]);
            d = ii(d, a, b, c, x[3],  S[13], T[53]);
            c = ii(c, d, a, b, x[10], S[14], T[54]);
            b = ii(b, c, d, a, x[1],  S[15], T[55]);
            a = ii(a, b, c, d, x[8],  S[12], T[56]);
            d = ii(d, a, b, c, x[15], S[13], T[57]);
            c = ii(c, d, a, b, x[6],  S[14], T[58]);
            b = ii(b, c, d, a, x[13], S[15], T[59]);
            a = ii(a, b, c, d, x[4],  S[12], T[60]);
            d = ii(d, a, b, c, x[11], S[13], T[61]);
            c = ii(c, d, a, b, x[2],  S[14], T[62]);
            b = ii(b, c, d, a, x[9],  S[15], T[63]);
            
            abcd[0] += a;
            abcd[1] += b;
            abcd[2] += c;
            abcd[3] += d;
        }
        
        /***************************************************************************************/
        // 以下为MD5更新数字签名的非线性函数
        /***************************************************************************************/
        
        /** a = ((a + q + x + t) << s) + b */
        private static int cc(int q, int a, int b, int c, int d, int x, int s, int t)
        {
            a += q + x + t;
            a = (a << s) | (a >>> (32 - s));
            return a + b;
        }
        
        /** a = b + ((a + f(b,c,d) + x + t) << s) */
        private static int ff(int a, int b, int c, int d, int x, int s, int t)
        {
            return cc(f(b, c, d), a, b, c, d, x, s, t);
        }
        
        /** a = b + ((a + g(b,c,d) + x + t) << s) */
        private static int gg(int a, int b, int c, int d, int x, int s, int t)
        {
            return cc(g(b, c, d), a, b, c, d, x, s, t);
        }
        
        /** a = b + ((a + h(b,c,d) + x + t) << s) */
        private static int hh(int a, int b, int c, int d, int x, int s, int t)
        {
            return cc(h(b, c, d), a, b, c, d, x, s, t);
        }
        
        /** a = b + ((a + i(b,c,d) + x + t) << s) */
        private static int ii(int a, int b, int c, int d, int x, int s, int t)
        {
            return cc(i(b, c, d), a, b, c, d, x, s, t);
        }
        
        /** f(x, y, z) = (x & y) | ((~x) & z) */
        private static int f(int x, int y, int z)
        {
            return (x & y) | ((~x) & z);
        }
        
        /** g(x, y, z) = (x & z) | (y & (~z)) */
        private static int g(int x, int y, int z)
        {
            return (x & z) | (y & (~z));
        }
        
        /** h(x, y, z) = x ^ y ^ z */
        private static int h(int x, int y, int z)
        {
            return x ^ y ^ z;
        }
        
        /** i(x, y, z) = y ^ (x | (~z)) */
        private static int i(int x, int y, int z)
        {
            return y ^ (x | (~z));
        }
        
        /** 读取数组中指定起始和结束标志的新数组 */
        private static int[] slice(int[] m, int start, int end)
        {
            int len = end - start, index = 0;
            int[] x = new int[len];
            for (int i=start;i<end;i++) {
                x[index++] = m[i];
            }
            return x;
        }
        
        /***************************************************************************************/
        // 以下为MD5外部使用的方法
        /***************************************************************************************/
        
        /**
         * MD5 编码，UTF8
         *
         * @param src 源串
         * @return 目标串
         */
        public static String encodeUTF8(String src)
        {
            return encode(src, _UTF_8_);
        }
        
        /**
         * MD5编码，指定encding
         *
         * @param src 源串
         * @param encoding 编码
         * @return 目标串
         */
        public static String encode(String src, String encoding)
        {
            byte[] destBytes = encodeByte(src, encoding);
            return Hexs.toHexString(destBytes);
        }
        
        /**
         * MD5编码，指定encding
         *
         * @param src 源串
         * @return 目标串
         */
        public static String encode(byte[] src)
        {
            byte[] destBytes = encodeByte(src);
            return Hexs.toHexString(destBytes);
        }
        
        /**
         * MD5编码,返回byte数组
         *
         * @param src 源串
         * @return 目标编码
         */
        public static byte[] encodeByte(String src, String encoding)
        {
            Asserts.nonNull(src, _SRC_);
            Asserts.nonNull(encoding, _ENCODING_);
            
            try
            {
                return encodeByte(src.getBytes(encoding));
            }
            catch (UnsupportedEncodingException e)
            {
                throw Asserts.exception("非法的编码");
            }
        }
        /**
         * MD5编码,返回byte数组
         *
         * @param src 源byte数组
         * @return 目标编码
         */
        public static byte[] encodeByte(byte[] src)
        {
            return new MD5Coder().digest(src);
        }
    }
}
